﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using GA.Server.Api;
using System.Net.Sockets;
using System.Net;
using GA.Server.Configuration;
using System.ServiceModel;
using System.Security.Cryptography;
using GA.Shared.Interfaces;
using GA.Shared.Objects;
using System.Windows.Data;
using System.Net.Security;

namespace GA.Server.ConnectionListener
{
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
    public class ConnectionListenerServer : GAServer, IConnection
    {
        /// <summary>
        /// Gets the Name.
        /// </summary>
        public override string Name
        {
            get { return "ConnectionListenerServer"; }
        }

        /// <summary>
        /// Register user function.
        /// </summary>
        public Func<Authentication, User> RegisterUser { get; set; }

        /// <summary>
        /// Authenticate user function.
        /// </summary>
        public Func<Authentication, User> AuthenticateUser { get; set; }

        /// <summary>
        /// Get IV function.
        /// </summary>
        public Func<IPAddress, byte[]> GetIVFunc { get; set; }

        /// <summary>
        /// Get Key function.
        /// </summary>
        public Func<byte[]> GetKeyFunc { get; set; }

        /// <summary>
        /// Default constructor.
        /// </summary>
        public ConnectionListenerServer()
        {
        }

        /// <summary>
        /// Creates a new ConnectionListenerServer with the specified properties.
        /// </summary>
        /// <param name="properties">Properties to pass.</param>
        /// <exception cref="ArgumentNullException">Thrown if properties is null.</exception>
        public ConnectionListenerServer(ServerPropertiesElementCollection properties)
            : base(properties)
        {
            if (properties == null) throw new ArgumentNullException("properties");

            mBaseUri = properties.Get("BaseUri").Value;
            mAddressUri = properties.Get("AddressUri").Value;
            mObjectServers = new Dictionary<string, IObjectServer>();
            mMaxConnections = int.Parse(properties.Get("MaxConnections").Value);
        }

        #region IConnection Members



        #endregion

        public void RegisterObjectServer<T>(IObjectServer objectServer) where T : LinkedDependencyObject
        {
            mObjectServers.Add(typeof(T).Name, objectServer);
        }

        protected override void StartCore()
        {
            Status = GAServerStatus.Starting;
            BasicHttpBinding basicHttpBinding = new BasicHttpBinding()
            {
                Name = "ConnectionListenerServer"
            };

            basicHttpBinding.Security.Mode = BasicHttpSecurityMode.TransportWithMessageCredential;
            basicHttpBinding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Basic;

            Uri baseAddress = new Uri(mBaseUri);
            Uri address = new Uri(mBaseUri + mAddressUri);

            using (ServiceHost serviceHost = new ServiceHost(this, baseAddress))
            {
                serviceHost.AddServiceEndpoint(typeof(IConnection), basicHttpBinding, address);
                serviceHost.Open();
                Log.InfoFormat("ServiceHost opening and listening at {0}...", mBaseUri + mAddressUri);
                Status = GAServerStatus.Running;
                while (!StopProcess)
                {
                    System.Threading.Thread.Sleep(50);
                }
                Status = GAServerStatus.ShuttingDown;
                Log.Info("ServiceHost closed.");
                serviceHost.Close();
            }
            Status = GAServerStatus.Stopped;
        }

        private Dictionary<string, IObjectServer> mObjectServers;
        private int mMaxConnections;
        private string mBaseUri;
        private string mAddressUri;

        #region IConnection Members

        public SerializableTransferObject CreateObject(SerializableTransferObject authorizedUser, SerializableTransferObject newObject)
        {
            if (authorizedUser == null) throw new ArgumentNullException("authorizedUser");
            if (newObject == null) throw new ArgumentNullException("newObject");
            if (!mObjectServers.ContainsKey(newObject.OutputType)) throw new InvalidOperationException(string.Format("Server for the specified type {0} is not online.", newObject.GetType()));

            return mObjectServers[newObject.OutputType].CreateObject(authorizedUser, newObject);
        }

        public void EditObject(SerializableTransferObject authorizedUser, SerializableTransferObject editObject)
        {
            if (authorizedUser == null) throw new ArgumentNullException("authorizedUser");
            if (editObject == null) throw new ArgumentNullException("editObject");
            if (!mObjectServers.ContainsKey(editObject.OutputType)) throw new InvalidOperationException(string.Format("Server for the specified type {0} is not online.", editObject.GetType()));

            mObjectServers[editObject.OutputType].EditObject(authorizedUser, editObject);
        }

        public void DeleteObject(SerializableTransferObject authorizedUser, SerializableTransferObject deleteObject)
        {
            if (authorizedUser == null) throw new ArgumentNullException("authorizedUser");
            if (deleteObject == null) throw new ArgumentNullException("deleteObject");
            if (!mObjectServers.ContainsKey(deleteObject.OutputType)) throw new InvalidOperationException(string.Format("Server for the specified type {0} is not online.", deleteObject.GetType()));

            mObjectServers[deleteObject.OutputType].DeleteObject(authorizedUser, deleteObject);
        }

        public SerializableTransferObject FindObject(SerializableTransferObject authorizedUser, string type, Dictionary<string, object> parameters)
        {
            if (authorizedUser == null) throw new ArgumentNullException("authorizedUser");
            if (parameters == null) throw new ArgumentNullException("parameters");
            if (!mObjectServers.ContainsKey(type)) throw new InvalidOperationException(string.Format("Server for the specified type {0} is not online.", type));

            return mObjectServers[type].GetObject(authorizedUser, type, parameters);
        }

        public IEnumerable<SerializableTransferObject> FindObjects(SerializableTransferObject authorizedUser, string type, Dictionary<string, object> parameters)
        {
            if (authorizedUser == null) throw new ArgumentNullException("authorizedUser");
            if (parameters == null) throw new ArgumentNullException("parameters");
            if (!mObjectServers.ContainsKey(type)) throw new InvalidOperationException(string.Format("Server for the specified type {0} is not online.", type));

            return mObjectServers[type].GetObjects(authorizedUser, type, parameters);
        }

        /// <summary>
        /// Gets an IV for a specified IpAddress.
        /// </summary>
        /// <param name="ipAddress">IPAddress to assign to.</param>
        /// <returns>IV byte array for the specified IPAddress.</returns>
        /// <remarks>This IV lasts for one authorization attempt, and only for this specific IP. The IV is killed as soon as an authorization attempt is run.</remarks>
        /// <exception cref="ArgumentNullException">Thrown if ipAddress is null.</exception>
        /// <exception cref="ArgumentException">Thrown if ipAddress is null.</exception>
        public byte[] GetIV(IPAddress ipAddress)
        {
            if (ipAddress == null) throw new ArgumentNullException("ipAddress");
            if (GetIVFunc == null) throw new InvalidOperationException("User authentication has not setup yet.");

            return GetIVFunc.Invoke(ipAddress);
        }

        /// <summary>
        /// Gets the key for authentication communication.
        /// </summary>
        /// <returns>Byte array key.</returns>
        /// <remarks>This key lasts as long as the server doesn't restart.</remarks>
        /// <exception cref="InvalidOperationException"
        public byte[] GetKey()
        {
            if (GetKeyFunc == null) throw new InvalidOperationException("User authentication has not setup yet.");

            return GetKeyFunc.Invoke();
        }

        public object Authenticate(Authentication authentication)
        {
            if (authentication == null) throw new ArgumentNullException("authentication");
            if (AuthenticateUser == null) throw new InvalidOperationException("User registration has not been setup yet.");

            User baseObject = AuthenticateUser.Invoke(authentication);
            if (baseObject != null)
            {
                return new User(baseObject, BindingMode.OneWay)
                {
                    AuthenticationKey = baseObject.AuthenticationKey
                };
            }
            return null;
        }

        public SerializableTransferObject CreateUser(Authentication authentication)
        {
            if (authentication == null) throw new ArgumentNullException("authentication");
            if (RegisterUser == null) throw new InvalidOperationException("User registration has not been setup yet.");

            User baseObject = RegisterUser.Invoke(authentication);
            if (baseObject != null)
            {
                return new SerializableTransferObject(new User(baseObject, BindingMode.OneWay)
                {
                    AuthenticationKey = baseObject.AuthenticationKey
                });
            }
            return null;
        }

        #endregion

    }
}
